unit expcurveformunit;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls,
  ComCtrls, Spin, ButtonPanel, BGRABitmap, LCLTranslator;

type

  { TExpCurveForm }

  TExpCurveForm = class(TForm)
    PreviewPaintBox: TPaintBox;
    ResultButtonPanel: TButtonPanel;
    ExtraValueGroupBox: TGroupBox;
    AccelAGroupBox: TGroupBox;
    AccelBGroupBox: TGroupBox;
    AccelASpinEdit: TSpinEdit;
    AccelATrackBar: TTrackBar;
    DisplayModeLabel: TLabel;
    ExpForNextLevelRadioButton: TRadioButton;
    BaseValueGroupBox: TGroupBox;
    AccelBSpinEdit: TSpinEdit;
    AccelBTrackBar: TTrackBar;
    BaseValueSpinEdit: TSpinEdit;
    ExtraValueSpinEdit: TSpinEdit;
    BaseValueTrackBar: TTrackBar;
    ExtraValueTrackBar: TTrackBar;
    ValueChangePanel: TPanel;
    TotalExpRadioButton: TRadioButton;
    procedure AccelASpinEditChange(Sender: TObject);
    procedure AccelATrackBarChange(Sender: TObject);
    procedure AccelBSpinEditChange(Sender: TObject);
    procedure AccelBTrackBarChange(Sender: TObject);
    procedure BaseValueSpinEditChange(Sender: TObject);
    procedure BaseValueTrackBarChange(Sender: TObject);
    procedure CancelButtonClick(Sender: TObject);
    procedure DisplayModeRadioButtonChange(Sender: TObject);
    procedure ExtraValueSpinEditChange(Sender: TObject);
    procedure ExtraValueTrackBarChange(Sender: TObject);
    procedure OKButtonClick(Sender: TObject);
    procedure PreviewPaintBoxPaint(Sender: TObject);
    { Draws graph image, used as a background for the preview image. Its width
    is not equal to the preview image, and is expected to be stretched. Return
    value of this function must be freed. }
    function DrawGraphImage: TBGRABitmap;
    { Return experience needed for a given level }
    function ExpForLevel(Level: Integer): Integer;
    function ExpFromPrevLevel(Level: Integer): Integer;
  private
    ResultWasAccepted: Boolean;
  public
    BaseValue, ExtraValue, AccelA, AccelB: Integer;
    function ShowExpCurveSetup(ABaseValue, AExtraValue, AAccelA, AAccelB: Integer): Boolean;
  end;

var
  ExpCurveForm: TExpCurveForm;

implementation

uses
  BGRABitmapTypes, Math;

{$R *.lfm}

{ TExpCurveForm }

procedure TExpCurveForm.BaseValueTrackBarChange(Sender: TObject);
begin
  BaseValue := BaseValueTrackBar.Position;
  BaseValueSpinEdit.Value := BaseValue;
  Refresh;
end;

procedure TExpCurveForm.CancelButtonClick(Sender: TObject);
begin
  ResultWasAccepted := False;
  Close
end;

procedure TExpCurveForm.DisplayModeRadioButtonChange(Sender: TObject);
begin
  Refresh
end;

procedure TExpCurveForm.ExtraValueSpinEditChange(Sender: TObject);
begin
  ExtraValue := ExtraValueSpinEdit.Value;
  ExtraValueTrackBar.Position := ExtraValue;
  Refresh;
end;

procedure TExpCurveForm.ExtraValueTrackBarChange(Sender: TObject);
begin
  ExtraValue := ExtraValueTrackBar.Position;
  ExtraValueSpinEdit.Value := ExtraValue;
  Refresh;
end;

procedure TExpCurveForm.OKButtonClick(Sender: TObject);
begin
  ResultWasAccepted := True;
  Close
end;

procedure TExpCurveForm.PreviewPaintBoxPaint(Sender: TObject);
var
  ImgWidth, ImgHeight: Integer;
  GraphImage, FinalImage: TBGRABitmap;
  StretchedImage: TBGRACustomBitmap;

  Row: Integer;
  Column, X, Y, LvTextShift: Integer;
  NumColumns, ItemsInColumn, I: Integer;
  ToNext: Boolean;
begin
  { TODO: repaint the preview }
  GraphImage := DrawGraphImage;

  ImgWidth := PreviewPaintBox.Width;
  ImgHeight := PreviewPaintBox.Height;

  FinalImage := TBGRABitmap.Create(ImgWidth, ImgHeight, clWhite);

  StretchedImage := GraphImage.Resample(ImgWidth, ImgHeight);
  FinalImage.Assign(StretchedImage);
  StretchedImage.Free;
  GraphImage.Free;

  { TODO: find number of columns and rows dynamically? }
  NumColumns := 5;
  ItemsInColumn := 20;

  ToNext := ExpForNextLevelRadioButton.Checked;

  I := 1;
  FinalImage.CanvasBGRA.Font.Height := Min(20, ImgHeight div ItemsInColumn);
  if ImgHeight > ImgWidth then
    FinalImage.CanvasBGRA.Font.Height := Round(FinalImage.CanvasBGRA.Font.Height * ImgWidth / ImgHeight * 0.8);
  FinalImage.CanvasBGRA.Brush.Style := bsClear;
  LvTextShift := FinalImage.CanvasBGRA.TextWidth('L99: ');
  for Column := 1 to NumColumns do
    for Row := 1 to ItemsInColumn do begin
      if (I > 98) and (ToNext or (I > 99)) then
        Break;

      X := (Column - 1) * (ImgWidth div NumColumns);
      Y := (Row - 1) * (ImgHeight div ItemsInColumn);
      FinalImage.CanvasBGRA.Font.Color := clBlack;

      FinalImage.CanvasBGRA.TextOut(X, Y, 'L' + IntToStr(I) + ': ');

      X := X + LvTextShift;
      if ToNext then begin
        FinalImage.CanvasBGRA.Font.Color := clGreen;
        FinalImage.CanvasBGRA.TextOut(X, Y, IntToStr(ExpFromPrevLevel(I + 1)));
      end else begin
        FinalImage.CanvasBGRA.Font.Color := clRed;
        FinalImage.CanvasBGRA.TextOut(X, Y, IntToStr(ExpForLevel(I)));
      end;

      Inc(I);
    end;

  FinalImage.Draw(PreviewPaintBox.Canvas, 0, 0, True);

  FinalImage.Free;
end;

procedure TExpCurveForm.BaseValueSpinEditChange(Sender: TObject);
begin
  BaseValue := BaseValueSpinEdit.Value;
  BaseValueTrackBar.Position := BaseValue;
  Refresh;
end;

procedure TExpCurveForm.AccelATrackBarChange(Sender: TObject);
begin
  AccelA := AccelATrackBar.Position;
  AccelASpinEdit.Value := AccelA;
  Refresh;
end;

procedure TExpCurveForm.AccelBSpinEditChange(Sender: TObject);
begin
  AccelB := AccelBSpinEdit.Value;
  AccelBTrackBar.Position := AccelB;
  Refresh;
end;

procedure TExpCurveForm.AccelBTrackBarChange(Sender: TObject);
begin
  AccelB := AccelBTrackBar.Position;
  AccelBSpinEdit.Value := AccelB;
  Refresh;
end;

procedure TExpCurveForm.AccelASpinEditChange(Sender: TObject);
begin
  AccelA := AccelASpinEdit.Value;
  AccelATrackBar.Position := AccelA;
  Refresh;
end;

function TExpCurveForm.DrawGraphImage: TBGRABitmap;
var
  BarWidth, ImgWidth, ImgHeight, CurrentExp, CurrentY: Integer;
  Img: TBGRABitmap;
  I, CurrentX: Integer;
begin
  BarWidth := Max(1, PreviewPaintBox.Width div 98);
  ImgWidth := BarWidth * 98;
  ImgHeight := PreviewPaintBox.Height;
  Img := TBGRABitmap.Create(ImgWidth, ImgHeight, clWhite);

  Img.CanvasBGRA.Brush.Color:= ColorToBGRA(clSilver);
  for I := 1 to 99 do begin
    CurrentExp := ExpForLevel(I);

    CurrentY := ImgHeight;
    if CurrentExp <> 0 then
      CurrentY := ImgHeight - (CurrentExp * ImgHeight div 7738286);
    CurrentX := (I - 1) * BarWidth;
    Img.CanvasBGRA.FillRect(CurrentX - 1, ImgHeight, CurrentX + BarWidth, CurrentY);
  end;

  DrawGraphImage := Img;
end;

function TExpCurveForm.ExpForLevel(Level: Integer): Integer;
begin
  (* This formula is adapted from RPG Maker MV's corescript, from
    Game_Actor.prototype.expForLevel. Used under the MIT license. *)
  ExpForLevel := Round(
                    BaseValue
                    * (Power(Level - 1, 0.9 + AccelA / 250))
                    * Level
                    * (Level+1)
                    / (6 + Power(Level,2) / 50 / AccelB)
                  + (Level - 1) * ExtraValue
                 );
end;

function TExpCurveForm.ExpFromPrevLevel(Level: Integer): Integer;
begin
  ExpFromPrevLevel := ExpForLevel(Level) - ExpForLevel(Level - 1);
end;

function TExpCurveForm.ShowExpCurveSetup(ABaseValue, AExtraValue, AAccelA,
  AAccelB: Integer): Boolean;
begin
  ResultWasAccepted := False;
  BaseValue := ABaseValue;
  ExtraValue := AExtraValue;
  AccelA := AAccelA;
  AccelB := AAccelB;

  BaseValueSpinEdit.Value := BaseValue;
  BaseValueTrackBar.Position := BaseValue;
  ExtraValueSpinEdit.Value := ExtraValue;
  ExtraValueTrackBar.Position := ExtraValue;
  AccelASpinEdit.Value := AccelA;
  AccelATrackBar.Position := AccelA;
  AccelBSpinEdit.Value := AccelB;
  AccelBTrackBar.Position := AccelB;

  ShowModal;

  ShowExpCurveSetup := ResultWasAccepted
end;

end.

